﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Xml.Linq;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using OneNote = Microsoft.Office.Interop.OneNote;

namespace ESJImporter
{
    public partial class EdgeJournalDisplay : Form
    {
        ESJournal DisplayJournal;
        private Preferences pref_Settings;

        public EdgeJournalDisplay(string journalname, Preferences prefs)
        {
            InitializeComponent();
            pref_Settings = prefs;
            showXmlView(false);

            readEdgeFile(journalname);
            displayXML(rtb_ESJXML, DisplayJournal.esjxmldoc);
        }

        /// <summary>
        /// read the selected file into the tree display
        /// </summary>
        /// <param name="filename"></param>
        private void readEdgeFile(string filename)
        {
            DisplayJournal = new ESJournal(filename);

            // read the pages into the tree view
            tree_ESJXML.Nodes.Clear();

            TreeNodeCollection tnc_tab = tree_ESJXML.Nodes;
            for (int n = 0; n < DisplayJournal.edgejournalpages.Count; n++)
            {
                // todo: pagecontent: how to get a proper name
                if (((ESJournalPage)DisplayJournal.edgejournalpages[n]).tabname != "")
                {
                    TreeNode tn_tab = tree_ESJXML.Nodes.Add(((ESJournalPage)DisplayJournal.edgejournalpages[n]).tabname, ((ESJournalPage)DisplayJournal.edgejournalpages[n]).tabname);
                    tnc_tab = tn_tab.Nodes;
                }
                TreeNode tn_page = tnc_tab.Add(((ESJournalPage)DisplayJournal.edgejournalpages[n]).pagename, ((ESJournalPage)DisplayJournal.edgejournalpages[n]).pagecontent.Name.ToString() + n.ToString());
                // show strokes?
                //tn_page.Nodes.Add(strokeguid, XMLStroke.Name.ToString() + strokecounter.ToString());
            }
            tree_ESJXML.ExpandAll();
            if (tree_ESJXML.Nodes.Count > 0)
                tree_ESJXML.SelectedNode = tree_ESJXML.Nodes[0];
        }

        /// <summary>
        /// hide or show the xml content of the edge journal
        /// </summary>
        /// <param name="show"></param>
        private void showXmlView(Boolean show)
        {
            split_Main.Panel2Collapsed = !show;
            panel_XMLHidden.Visible = !show;
            panel_XMLVisible.Visible = show;
        }

        private void label_HideXMLView_Click(object sender, EventArgs e)
        {
            showXmlView(false);
        }

        private void label_ShowXMLView_Click(object sender, EventArgs e)
        {
            showXmlView(true);
        }

        /// <summary>
        /// display the selected page
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tree_ESJXML_AfterSelect(object sender, TreeViewEventArgs e)
        {
            panel_Drawing.Invalidate();
        }

        /// <summary>
        /// display the selected page in the preview window
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void panel_Drawing_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.Clear(Color.White);
            TreeNode pgnode = tree_ESJXML.SelectedNode;
            if (pgnode == null) return;
            if (pgnode.Nodes.Count > 0) return;

            // get selected page
            ESJournalPage curpage = DisplayJournal.getpage(pgnode.Name);
            highlightPage(pgnode.Name);

            // draw whole page
            for (int n = 0; n < curpage.pagestrokes.Count; n++)
            {
                drawStroke((XElement)curpage.pagestrokes[n]);
            }
        }

        /// <summary>
        /// draw a journal stroke according to the custom preferences
        /// </summary>
        /// <param name="xstroke"></param>
        public void drawStroke(XElement xstroke)//, Pen strokepen)
        {
            Graphics g = panel_Drawing.CreateGraphics();
            Pen strokepen = new Pen(Color.Black, 1);
            
            // adjust width according to preferences
            string esjwidth = xstroke.Attribute("width").Value;
            strokepen.Width = pref_Settings.getWidth(esjwidth);

            // adjust colour according to preferences
            string esjcolor = xstroke.Attribute("color").Value;
            strokepen.Color = pref_Settings.getColor(esjcolor);
            
            char[] seps = { ' ' };
            string[] strvalues = xstroke.Value.Split(seps);
            //ArrayList points = new ArrayList();
            double xps_prev = double.Parse(strvalues[0]);
            double yps_prev = double.Parse(strvalues[1]);
            for (int i = 2; i < strvalues.Length - 1; i += 2)
            {
                g.DrawLine(strokepen,
                    float.Parse(strvalues[i - 2]),
                    float.Parse(strvalues[i - 1]),
                    float.Parse(strvalues[i]),
                    float.Parse(strvalues[i + 1]));
            }
        }

        #region display & highlight xml of the Edge Journal
        /// <summary>
        /// display the whole xml in a formatted fashion
        /// </summary>
        /// <param name="rtb_xml"></param>
        /// <param name="xml_display"></param>
        private void displayXML(RichTextBox rtb_xml, XElement xml_display)
        {
            rtb_xml.Clear();
            displayXMLNode(rtb_xml, xml_display, "");
        }

        /// <summary>
        /// display a single node
        /// </summary>
        /// <param name="rtb_xml"></param>
        /// <param name="xml_node"></param>
        /// <param name="indent"></param>
        private void displayXMLNode(RichTextBox rtb_xml, XNode xml_node, string indent)
        {
            if (xml_node.NodeType == System.Xml.XmlNodeType.Element)
            {
                XElement xml_element = (XElement)xml_node;
                rtb_xml.Text += indent + "<";
                // name
                rtb_xml.Text += xml_element.Name.LocalName;
                // attributes
                foreach (XAttribute xml_attribute in xml_element.Attributes())
                {
                    rtb_xml.Text += " " + xml_attribute.Name + "=\"" + xml_attribute.Value + "\"";
                }
                if ((xml_element.Nodes().Count() == 0) && (xml_element.Value.Replace("\n", "") == ""))
                {
                    rtb_xml.Text += "/";
                }
                rtb_xml.Text += ">" + System.Environment.NewLine;
                // children
                if (xml_element.Nodes().Count() > 0)
                {
                    foreach (XNode xml_subnode in xml_element.Nodes())
                    {
                        displayXMLNode(rtb_xml, xml_subnode, indent + "  ");
                    }
                }
                // value (if existent)
                if (xml_element.Value.Replace("\n", "") != "")
                {
                    //rtb_xml.Text += xml_element.Value + System.Environment.NewLine;
                    rtb_xml.Text += indent + "</" + xml_element.Name + ">" + System.Environment.NewLine;
                }
            }
            if (xml_node.NodeType == System.Xml.XmlNodeType.Text)
            {
                string xtxt = ((XText)xml_node).Value.Replace("\n", "");
                if (xtxt != "")
                    rtb_xml.Text += indent + xtxt + System.Environment.NewLine;
            }
        }

        /// <summary>
        /// highlight the selected page
        /// </summary>
        /// <param name="pageid"></param>
        private void highlightPage(string pageid)
        {
            // workaround: find pageid in tree => number 
            int n = getPageNumber(tree_ESJXML.Nodes, pageid)+1;
            rtb_ESJXML.SelectAll();
            rtb_ESJXML.SelectionBackColor = DefaultBackColor;
            rtb_ESJXML.SelectionColor = DefaultForeColor;


            int selstart=0;
            for (int t = 0; t < n; t++)
            {
                selstart = rtb_ESJXML.Find("<page", selstart + 1, RichTextBoxFinds.WholeWord);
            }
            int selend = rtb_ESJXML.Find("<page", selstart + 1, RichTextBoxFinds.WholeWord);
            if (selend == -1)
                selend = rtb_ESJXML.Find("</xournal", selstart + 1, RichTextBoxFinds.WholeWord)+1;
            rtb_ESJXML.Select(selstart - 2, selend - selstart);
            rtb_ESJXML.SelectionBackColor = Color.DarkGray;
            rtb_ESJXML.SelectionColor = Color.White;
        }

        /// <summary>
        /// find the pageid in the tree, return the "flattened" index
        /// </summary>
        /// <param name="tree"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        private int getPageNumber(TreeNodeCollection tree, string key)
        {
            int ct = 0;
            for (int n=0; n<tree.Count; n++)
            {
                if (tree[n].Name == key)
                {
                    return ct;
                }
                if (tree[n].Nodes.Count>0)
                {
                    if (tree[n].Nodes.Find(key, false).Count() >0)
                    {
                        return ct + getPageNumber(tree[n].Nodes, key);
                    }
                    else
                    {
                        ct += tree[n].Nodes.Count-1;
                    }
                }
                ct++;
            }
            return ct;
        }

        /// <summary>
        /// return a flattened list of CHECKED nodes
        /// </summary>
        /// <param name="tree"></param>
        /// <returns></returns>
        private TreeNode[] checkedNodesFlat(TreeNodeCollection tree)
        {
            TreeNode tn_flatnodes = new TreeNode();
            for (int n = 0; n < tree.Count; n++)
            {
                if (tree[n].Nodes.Count > 0)
                {
                    tn_flatnodes.Nodes.AddRange(checkedNodesFlat(tree[n].Nodes));
                }
                else
                {
                    if (tree[n].Checked)
                    {
                        tn_flatnodes.Nodes.Add((TreeNode) tree[n].Clone());
                    }
                }
            }
            TreeNode[] tnresults = new TreeNode[tn_flatnodes.Nodes.Count];
            tn_flatnodes.Nodes.CopyTo(tnresults,0);
            return tnresults;
        }
        #endregion

        /// <summary>
        /// (un)check all pages, if tab was checked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tree_ESJXML_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (e.Node.Nodes.Count > 0)
            {
                foreach (TreeNode tn in e.Node.Nodes)
                    tn.Checked = e.Node.Checked;
            }
        }

        /// <summary>
        /// (un)check all nodes, if "select all" was (un)checked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cb_SelectAll_CheckedChanged(object sender, EventArgs e)
        {
            foreach (TreeNode tn in tree_ESJXML.Nodes)
            {
                tn.Checked = cb_SelectAll.Checked;
            }
        }

        /// <summary>
        /// convert the selected pages directly to onenote page(s)
        /// in principle: classical behaviour
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void convertToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DirectConverter dlg_DirectConversion = new DirectConverter();
            if (countCheckedNodes(tree_ESJXML.Nodes) > 1)
            {
                dlg_DirectConversion.cb_CreateNewPages.Checked = true;
                dlg_DirectConversion.cb_CreateNewPages.Enabled = false;
            }
            if (dlg_DirectConversion.ShowDialog() == DialogResult.OK)
            {   // execute conversion:
                // iterate through all pages and insert them in OneNote
                // either create new pages or add to selected
                // Todo: deal with tabs and sections (currently direct page to page) - may be ignored, as this belongs to the main level

                // get current section or page
                // note: if no new pages are to be created, the selection MUST be a page
                string onenoteID = dlg_DirectConversion.tree_OneNoteHierarchy.SelectedNode.Name;
                string sectionID = onenoteID;
                string pageID = "";

                string XMLPageContent;
                OneNote.Application onenoteApp = new OneNote.Application();
                onenoteApp.GetPageContent(onenoteID, out XMLPageContent, Microsoft.Office.Interop.OneNote.PageInfo.piAll);

                XElement xpage = XElement.Parse(XMLPageContent);
                if (xpage.Name.LocalName == "Page")
                {
                    pageID = onenoteID;
                    if (dlg_DirectConversion.cb_CreateNewPages.Checked)
                    {
                        sectionID = dlg_DirectConversion.tree_OneNoteHierarchy.SelectedNode.Parent.Name;
                    }
                }
                else
                {
                    if (!dlg_DirectConversion.cb_CreateNewPages.Checked)
                    {
                        MessageBox.Show( "you need to select a destination PAGE, if you don't want to create a new page for your entry");
                        return;
                    }
                }

                // todo: use tabs ?
                // todo: delete existing pages ?
                foreach (TreeNode tn in checkedNodesFlat(tree_ESJXML.Nodes))    // ignore tabs
                {
                    // create page
                    if (dlg_DirectConversion.cb_CreateNewPages.Checked)
                        pageID = OneNoteFunctions.CreatePage(sectionID, tn.Text, false);
                    // generate strokes from page
                    System.Windows.Ink.StrokeCollection pagestrokes = EdgeJournalFunctions.createStrokeCollectionFromPage(DisplayJournal.getpage(tn.Name), pref_Settings);
                    // build xml & inject to OneNote
                    XElement serialisedDocument = OneNoteFunctions.buildOneNoteXML(pageID, pagestrokes, pref_Settings);
                    onenoteApp.UpdatePageContent(serialisedDocument.ToString(), DateTime.MinValue);
                }
            }
            this.DialogResult = DialogResult.None;
        }

        /// <summary>
        /// count the number of checked pages
        /// </summary>
        /// <param name="nodes"></param>
        /// <returns></returns>
        private int countCheckedNodes(TreeNodeCollection nodes)
        {
            int n = 0;
            foreach (TreeNode tn in nodes)
            {
                if (tn.Checked) n++;
                if (tn.Nodes.Count > 0) n = n + countCheckedNodes(tn.Nodes);
            }
            return n;
        }

        /// <summary>
        /// only allow conversion if at least one page is checked
        /// if the context is openend on a single page and no page is checked, then automatically check this page
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ctx_Journal_Opening(object sender, CancelEventArgs e)
        {
            convertToolStripMenuItem.Enabled = true;
            if (countCheckedNodes(tree_ESJXML.Nodes) == 0)
            {
                TreeNode tnselect = tree_ESJXML.GetNodeAt(tree_ESJXML.PointToClient(Control.MousePosition));
                if (tnselect == null)
                {
                    convertToolStripMenuItem.Enabled = false;
                    return;
                }
                tnselect.Checked = true;
            }
        }

    }
}
